﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FinanSoft.Utilities;

namespace FinanSoft.Entities
{
    public class AccountingCatalog
    {
        #region Atributos
        // Cuentas principales del árbol: Activos, Pasivos, etc
        private Account[] _PrincipalAccounts;
        private bool _IsError;
        private String _MessageError;

        
        #endregion

        #region Constructor
        public AccountingCatalog()
        {
            // Inicializa el arreglo de las cuentas principales
            _PrincipalAccounts = new Account[Constants.NUM_DEFAULT_ACCOUNTS];
            _IsError = false;
            _MessageError = "";
        }
        #endregion

        #region Propiedades get/set
        /// <summary>
        /// Retorna el arreglo de cuentas principales
        /// </summary>
        public Account[] PrincipalAccounts
        {
            get { return _PrincipalAccounts; }
        }

        public String MessageError
        {
            get { return _MessageError; }
            set { _MessageError = value; }
        }

        public bool IsError
        {
            get { return _IsError; }
            set { _IsError = value; }
        }
        #endregion

        #region Métodos públicos de la clase

        /// <summary>
        /// Devuelve las cuentas activas (es decir que no tienen hijos y se les puede hacer asientos) del catálogo contable
        /// </summary>
        /// <returns>Lista de las cuentas activas del catálogo</returns>
        public List<Account> GetActiveAccounts()
        {
            List<Account> activeAccounts = new List<Account>();
            foreach (Account account in _PrincipalAccounts)
                activeAccounts.AddRange(getChildrenAccount(account));
            return activeAccounts;
        }

        public List<Account> GetSpecialAccounts(Constants.SpecialType specialType)
        {
            List<Account> specialAccounts = new List<Account>();
            foreach (Account activeAccount in GetActiveAccounts())
                if (activeAccount.SpecialType == specialType)
                    specialAccounts.Add(activeAccount);
            return specialAccounts;
        }

        /// <summary>
        /// Devuelve todas las cuentas activas (es decir que no son título) que posee una cuenta como sus hijas. Si la cuenta que se da es
        /// una cuenta activa, entonces devuelve una lista que contiene unicamente a dicha cuenta.
        /// </summary>
        /// <param name="parent">Cuenta para la que se quiere sus cuentas activas</param>
        /// <returns>Lista con todas las cuentas activas de la cuenta brindada</returns>
        private List<Account> getChildrenAccount(Account parent)
        {
            List<Account> children = new List<Account>();
            if (!parent.IsTitle)
            {
                children.Add(parent);
                return children;
            }
            foreach (Account child in parent.Children.Values)
                children.AddRange(getChildrenAccount(child));
            return children;
        }

        /// <summary>
        /// Método que se encarga de agregar una nueva cuenta al catálogo
        /// contable, realiza las validaciones para ver si es posible o es
        /// correcto guardar dicha cuenta donde se solicita
        /// </summary>
        /// <param name="newAccount">Nueva cuenta a insertar en el catálogo</param>
        /// <param name="fatherID">ID de la cuenta padre</param>
        /// <returns>Retorna un booleano referente al éxito de la acción o no</returns>
        public bool AddNewAccount(Account newAccount, int fatherID)
        {
            for (int catalogIndex = 0; catalogIndex < _PrincipalAccounts.Length; catalogIndex++)
            {
                Account principalAccount = _PrincipalAccounts[catalogIndex];
                if (principalAccount.AccountID == fatherID)
                {
                    principalAccount.AddAccount(newAccount);
                    return true;
                }
                else
                {
                    int nivel = 0;
                    if (AddNewAccountAux(principalAccount, newAccount, fatherID, nivel++))
                        return true;
                    if (_IsError)
                        return false;
                }
            }
            _IsError = true;
            _MessageError = Constants.ERROR_ACCOUNT_NOT_FOUND;
            return false;
        }

        /// <summary>
        /// Método auxiliar para buscar la cuenta padre de la nueva cuenta entre los hijos del
        /// árbol, y realiza las validaciones del tipo de cuenta padre (si no es título da error)
        /// </summary>
        /// <param name="fatherAccount">Cuenta padre del árbol actual</param>
        /// <param name="newAccount">Cuenta nueva a agregar</param>
        /// <param name="fatherID">id de la cuenta padre para la nueva cuenta</param>
        /// <returns></returns>
        private bool AddNewAccountAux(Account fatherAccount, Account newAccount, int fatherID, int nivel)
        {
            // Se revisa que si es un título la cuenta que se va a agregar y está en nivel #5, ya no es posible agregar más
            if (newAccount.IsTitle && nivel >= Constants.MAX_NUM_LEVELS)
            {
                _IsError = true;
                _MessageError = Constants.ERROR_ACCOUNT_MAX_LEVEL;
                return false;
            }

            if (fatherAccount.IsTitle)
            {
                // Se corre por cada uno de los nodos hijos del padre, buscando al que tenga igual ID
                foreach (Account childAccount in fatherAccount.Children.Values)
                {
                    if (childAccount.AccountID == fatherID)
                    {
                        if (childAccount.IsTitle)
                        {
                            childAccount.AddAccount(newAccount);
                            return true;
                        }
                        else
                        {
                            // Error: El padre tomado no es un título, no se le puede agregar hijos
                            _IsError = true;
                            _MessageError = Constants.ERROR_ACCOUNT_FATHER_TITLE;
                            return false;
                        }
                    }
                    else
                        // Sino sigue buscando en los hijos del hijo.. recursivamente
                        if (this.AddNewAccountAux(childAccount, newAccount, fatherID, nivel++))
                            return true;
                }
            }
            // Si no encontró resultados
            return false;
        }

        /// <summary>
        /// Método que se encarga de eliminar una cuenta con el id especificado como parámetro
        /// </summary>
        /// <param name="idAccount">id de la cuenta a eliminar</param>
        /// <returns>Retorna true si la acción es exitosa, false si no</returns>
        public bool DeleteAccount(int idAccount)
        {
            for (int catalogIndex = 0; catalogIndex < _PrincipalAccounts.Length; catalogIndex++)
            {
                Account principalAccount = _PrincipalAccounts[catalogIndex];
                if (principalAccount.AccountID == idAccount)
                {
                    return false;
                    _IsError = true;
                    _MessageError = Constants.ERROR_ACCOUNT_FATHER_NOTITLE;
                }
                else
                {
                    if (DeleteAccountAux(principalAccount, idAccount))
                        return true;
                    if (_IsError)
                        return false;
                }
            }
            _IsError = true;
            _MessageError = Constants.ERROR_ACCOUNT_NOT_FOUND;
            return false;
        }

        /// <summary>
        /// Método auxiliar de eliminar cuenta, se encarga de buscar la cuenta a eliminar en
        /// los nodos del árbol del catálogo contable
        /// </summary>
        /// <param name="fatherAccount">Nodo padre del arbol actual</param>
        /// <param name="idAccount">id de la cuenta a eliminar</param>
        /// <returns>Retorna true si la accion fue exitosa, retorna false si hubo error o no encontró la c</returns>
        private bool DeleteAccountAux(Account fatherAccount, int idAccount)
        {
            if (fatherAccount.IsTitle)
            {
                // Se corre por cada uno de los nodos hijos del padre, buscando al que tenga igual ID
                foreach (Account childAccount in fatherAccount.Children.Values)
                {
                    if (childAccount.AccountID == idAccount)
                    {
                        if (childAccount.Children == null || childAccount.Children.Count == 0)
                        {
                            fatherAccount.Children.Remove(childAccount.AccountID);
                            return true;
                        }
                        else
                        {
                            _IsError = true;
                            _MessageError = Constants.ERROR_ACCOUNT_DELETE;
                            return false;
                        }
                    }
                    else
                        // Sino sigue buscando en los hijos del hijo.. recursivamente
                        if (this.DeleteAccountAux(childAccount, idAccount))
                            return true;
                }
            }
            // Si no encontró resultados
            return false;
        }

        /// <summary>
        /// Método que se encarga de editar una cuenta contenida en el catálogo
        /// contable, la busca dentro del árbol por medio de id
        /// </summary>
        /// <param name="account">Cuenta a modificar</param>
        /// <returns>Retorna true si la acción es exitosa, false si no</returns>
        public bool EditAccount(Account account)
        {
            for (int catalogIndex = 0; catalogIndex < _PrincipalAccounts.Length; catalogIndex++)
            {
                Account principalAccount = _PrincipalAccounts[catalogIndex];
                if (principalAccount.AccountID == account.AccountID)
                {
                    principalAccount.ForeignName = account.ForeignName;
                    principalAccount.AccountCurrency = account.AccountCurrency;
                    return true;
                }
                else
                {
                    if (EditAccountAux(principalAccount, account))
                        return true;
                    if (_IsError)
                        return false;
                }
            }
            _IsError = true;
            _MessageError = Constants.ERROR_ACCOUNT_NOT_FOUND;
            return false;
        }

        private bool EditAccountAux(Account fatherAccount, Account account)
        {
            if (fatherAccount.IsTitle)
            {
                foreach (Account childAccount in fatherAccount.Children.Values)
                {
                    if (childAccount.AccountID == account.AccountID)
                    {
                        childAccount.Name = account.Name;
                        childAccount.ForeignName = account.ForeignName;
                        childAccount.AccountCurrency = account.AccountCurrency;

                        return true;
                    }
                    else
                        // Sino sigue buscando en los hijos del hijo.. recursivamente
                        if (this.EditAccountAux(childAccount, account))
                            return true;
                }
            }
            // Si no encontró resultados
            return false;
        }
        #endregion
    }
}
